Level 0 | Level 1 | Level 2 | Level 3 | Level 4 |
Level 5 | Level 6 | Level 7 | Level 8 | Level 9 |
Level 10 | Level 11 | Level 12 | Level 13 | Level 14 |
Optimizing is a must if you want a good clean rip and a small file size that contains the music driver and no game data.
After you have made the rip and make sure every sound channel works and the header updated with name of game, composer, and
company name you are ready to optimize.
If you use NES2NSF then you have partially optimized the rip because you have a 16KB file section and usually this contains
all the data you need. Still you may need to trim the file more. Take the game 1943 in Level 1 and you can optimize this game.
Look at the end of the NSF and trim off all the extra FF bytes in a hex editor. This only trims the file alittle bit. If you
used this space for your tune arrangement code then don't trim this space.
Next thing you can do is disassemble the code if you haven't already. You notice the start of the music code is at $8000.
You can look through the disassembly till you run into some code that is not part of the music driver. You will run into
some code at $A902 so then you eliminate everything after. So how do I figure out how to find that address in the NSF.
Simple, all you do is use this formula, address +80(header) - 8000 = NSF Offset. Eliminate everything after 2982h. If you
arranged the tunes then you need to rewrite the code at the end of the music driver. Now load the NSF and you find out it
plays. The NSF is nice and trim now that it's optimized.
Now you need to learn how to optimize a rip from an emulator dump if the music driver is past $8000. Let's use Wrecking Crew
for an example. The play is at $F2E6 and the init is wherever you place some code. This time it's highly recommended that
you place the code at $FF80 and eliminate the rest of the data after this code. The rest of the data should be all FF's.
FF's or 00's are usually padded data at an end of a rom to make a bank add up to the size it's supposed to be. However you
can get rid of this data. Now move on to the next step.
Now look at your disassembly and next you want to figure out where to eliminate the data before the sound driver. First you
gotta figure out the start of the sound driver. Look at the play code and track every JSR or JMP that goes before the
address $F2E6 and next you want to check any LDA $xxxx,X or Y that reads in the sound driver area. After checking all this
you finally figure out that the start of the sound driver is about $F100 and so you eliminate everything before this address.
So how do you find this address again in the NSF. This time you do it alittle differently. Take the header off first and
then do this formula. address - 8000 = offset. The offset would be 7100h in the headerless NSF you have. Now eliminate
everything before that. Do not remove the byte at 7100h. Now put the header back on the NSF and restore the init and play
addresses. Next you will change the load address, yes that address that you have been wondering what to use for. Well, the
load address is the start of the program and you're changing the start of the program to $F100 so that's your load address
in the header. Remember to flip the bytes before entering into the header.
If you have any code that is writing to any other registers like $2006/$2007 for instance there is a slight possibility that
the game is picking up data in the CHR(Character ROM) for the music driver but this doesn't happen very often. So you can
zero out any data that is not sound code. Track the routine in the sound driver area and debug in an emulator if you have to
so that you can track down the data as well and other routines in the sound driver. Make sure that you also zero out any
bankswitching code. If the music driver uses this bankswitching code and the rip is not a bankswitching rip then you can
either make it return via a RTS or NOP out the code. Any writes to anything other then ram or expansion rom or saveram or
mirrored areas is illegal and must be changed. You can only write to sound registers, expansion sound chip registers, and
NSF bankswitching registers, and RAM areas.
This is the end of basic optimization of a NSF. Next you have Paging Optimization and this is alittle more difficult but you
can manage.
Paging Optimization
Paging optimization is a method to optimize 2 banks and shuffle one at the end and one at the beginning of the NSF. This
makes for a much smaller NSF rip then most simple optimizations like stated above. You don't have to design any code for
this as long as the code is linking the banks. All you do is switch the banks around and adjust the load address and the
bankswitching bytes in the header and you're done. This is a prelude to bankswitching and in the long run will help you
understand bankswitching.
You need to learn about the banks in a NSF first. NSF's have 4KB banks and there is 8 of them in the $8000 - $FFFF NES
address space. Each bankswitching byte in the header at 70h - 77h controls a 4KB bank and tells the player what bank to load
at what address location. Look to the following table.
next you can adjust the load address and you do this according to the spec, load address AND 0FFFh. This is to determine
where on the fist bank to load the data.
Now we can get down and dirty with an example. Remember Level 3, we will use the game and it's Shanghai 2 for the example NSF
to optimize. This first thing you want to do is get the rip that is 32KB in size and eliminate the banks that are not used
in the game. We do this by figuring out what banks are used by debugging the code. You open the disassembly and look at all
the code in the music driver and note the locations. All of the code is in bank 0 or $8000 - $8FFF area. Next you want to
debug the code to find out where the data is. Start with the data that is not DPCM. You have the following code.
Find a particular address with an indirect read operation and debug for the code location like so. This would be for the
first tune.
$8757:B1 81 LDA ($81),Y @ $933B = #$50 $8759:C8 INY $875A:9D E2 06 STA $06E2,X @ $06E2 = #$00 $875D:98 TYA
Indirect Indexed operations are useful for transferring and loading large blocks of data and in this case is loading part of
the sound driver data for the first tune. As you can see that some of the data is being loaded at $933B which is in page 1
of the NSF, $9000 - $9FFF. Next you can debug for some of the other tunes. In this case you can also check the init code.
$80AD:0A ASL $80AE:AA TAX $80AF:BD 9D 88 LDA $889D,X @ $889D = #$66 $80B2:85 81 STA $81 = #$00 $80B4:BD 9E 88 LDA $889E,X @ $889E = #$90 $80B7:85 82 STA $82 = #$00 $80B9:A0 00 LDY #$00 $80BB:B1 81 LDA ($81),Y @ $0000 = #$00 $80BD:30 E6 BMI $80A5
This code here loads data in page 1 and for the other tunes the data goes deeper into ROM. For tune 5 you load data at $AAA8
and is in page 2. Eventually you will run into data that uses page 3 as well. So now you know that banks 00-03 are used.
Next you need to locate the DPCM data.
Finding the DPCM data is not hard in this game and as a matter of fact you can look at the NES Audio Ripping Doc to help you
with that. Except that you don't have to move the samples, all you want to do is locate the bank the samples are in. Also
look at the APU reference to get the registers for the DPCM and read up on them as well. Now locate the code.
$8774:0A ASL $8775:A8 TAY $8776:B9 3D B4 LDA $B43D,Y @ $B43F = #$A0 $8779:8D 10 40 STA $4010 = #$FF $877C:C8 INY $877D:B9 3D B4 LDA $B43D,Y @ $B43F = #$A0 $8780:8D 11 40 STA $4011 = #$FF $8783:C8 INY $8784:B9 3D B4 LDA $B43D,Y @ $B43F = #$A0 $8787:8D 12 40 STA $4012 = #$FF $878A:C8 INY $878B:B9 3D B4 LDA $B43D,Y @ $B43F = #$A0 $878E:8D 13 40 STA $4013 = #$FF
This is the DPCM register write code and what you're looking for is the write to $4012 which will tell you where the samples
are located at. $4012 is loaded with the byte A0. So you do the following formula. Contents of $4012 * 40 + $C000. So you do
this A0 * 40 + $C000 = $E800. This is tune 1 and you can do it for the others as well. $E800 is located in page 06 and you
will find out page 07 is used as well. So you use pages 00 - 03 and 06 - 07. Next you piece them together.
Each 4KB bank is 1000h in size and so banks 00 - 03 is in the range of 0h - 3FFFh. Banks 06 and 07 are 6000h - 6FFFh and
7000h - 7FFFh respectively. So divide the file in half and take the upper 4000h and divide that in half and take the upper
2000h and append that to the bottom 16KB at the end of the file. So now you have banks 00 - 03 and 06 - 07 in one file and
you eliminated banks 04 and 05 from the NSF. Now you have one step to go. You have to set the bankswitching bytes in the
header. Yes it's nice that you can use the bankswitching bytes for something other then bankswitching and paging
optimizations is it. Since you relocated the banks, pages 06 and 07 become 04 and 05 in the NSF. You use the bankswitching
bytes in the header to determine what pages are loaded into what 4KB address space. 00 - 03 is $8000 - $BFFF and 06 - 07 is
$E000 - $FFFF. So you fill in the bytes at 70h - 77h in the header. 00,01,02,03,00,00,04,05 . You may notice that $C000 -
$DFFF is not used, that's because we eliminated the banks that didn't have music data. You leave the load address the same
for now because we have not changed the start of the program just yet. Next we are going to figure out the best banks to
trim.